﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using ScrewTurn.Wiki.PluginFramework;

namespace jkPlugin
{
    public class JK : IFormatterProviderV30
    {
        private IHostV30 _host;
        private string _config;
        private bool _enableLogging = true;
        private static readonly ComponentInformation Info = new ComponentInformation("JK's Category Counter Plugin", "Jaco", "1.0.0.1", "", "");

        //private static readonly Regex XmlRegex = new Regex(@"\<jk(.*?)\>(.*?)\<\/jk\>",
        private static readonly Regex XmlRegex = new Regex(@"\<jk(.*?)\/\>",
            RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);

        /// <summary>
        /// Specifies whether or not to execute Phase 1.
        /// </summary>
        public bool PerformPhase1
        {
            get { return false; }
        }

        /// <summary>
        /// Specifies whether or not to execute Phase 2.
        /// </summary>
        public bool PerformPhase2
        {
            get { return false; }
        }

        /// <summary>
        /// Specifies whether or not to execute Phase 3.
        /// </summary>
        public bool PerformPhase3
        {
            get { return true; }
        }

        /// <summary>
        /// Gets the execution priority of the provider (0 lowest, 100 highest).
        /// </summary>
        public int ExecutionPriority
        {
            get { return 50; }
        }

        /// <summary>
        /// Performs a Formatting phase.
        /// </summary>
        /// <param name="raw">The raw content to Format.</param>
        /// <param name="context">The Context information.</param>
        /// <param name="phase">The Phase.</param>
        /// <returns>The Formatted content.</returns>
        public string Format(string raw, ContextInformation context, FormattingPhase phase)
        {

            StringBuilder buffer = new StringBuilder(raw);

            KeyValuePair<int, string> block = FindAndRemoveFirstOccurrence(buffer);

            while (block.Key != -1)
            {
                string blockHash = "jk-" + block.Value.ToString();

                string result = null;

                if (System.Web.HttpContext.Current != null)
                {
                    result = System.Web.HttpContext.Current.Cache[blockHash] as string;
                }
                
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(block.Value);

                string category;
                string list;
                string zero;
                GetRootAttributes(doc, out category,out list,out zero);

                string JKInnerXml;
                JKInnerXml = "";//GetInnerXml(doc, out JKInnerXml);

                result = BuildResult(category,list,zero, JKInnerXml, context);
                
                if (System.Web.HttpContext.Current != null)
                {
                    System.Web.HttpContext.Current.Cache.Add(blockHash, result, null, DateTime.Now.AddMinutes(10),
                        System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);
                }            

                buffer.Insert(block.Key, result);

                block = FindAndRemoveFirstOccurrence(buffer);
            }

            return buffer.ToString();
        }

        /// <summary>
        /// Builds the result.
        /// </summary>
        /// <returns>The result.</returns>
        private static string BuildResult(string category, string list, string zero, string innerXml, ContextInformation context)
        {
            StringBuilder result = new StringBuilder();

            // the main body of the code starts here:

            int CategoryCount = 0;                      // counter of matching pages
            List<string> lnk = new List<string>();      // link to page (.FullName)
            List<string> nme = new List<string>();      // name of page (.Title)

            int i, j, k=0;
            string category_original;
            category_original = category;
            PageInfo testP1 = context.Page;
            IPagesStorageProviderV30 prov= testP1.Provider;
            string Pname = testP1.FullName.ToString();// pagename
            NamespaceInfo nspace;
            string snspace = ScrewTurn.Wiki.PluginFramework.NameTools.GetNamespace(Pname);// namespace as a string

            // an explicitly defined namespace overrides the default namespace of the page the plugin is called from..
            string[] cSplit = category.Split('.');
            if (cSplit.Length > 1)
            {               // namespace found
                snspace = cSplit[0];
                category = cSplit[1];
            }
                                                    // catch errors: an empty namespace means the root
            if (String.IsNullOrEmpty(snspace))
            {
                snspace = "";
                nspace = null;
            }
            else
            {
                nspace = context.Page.Provider.GetNamespace(snspace);
            }

            if (category != "save_as_draft")
            { //category=xxx|save_as_draft 
                                                        // count pages in a category
                ScrewTurn.Wiki.PluginFramework.PageInfo[] pi = prov.GetPages(nspace);// alle pagina's van de xx namespace ophalen
                for (j = 0; j < pi.Length; j++)
                {
                    ScrewTurn.Wiki.PluginFramework.CategoryInfo[] ci = prov.GetCategoriesForPage(pi[j]);// retrieve categories of page
                    if (ci.Length == 0 && category == "-")
                    {                                   // The argument: "-" is used to refer to the 'uncategorized' catagory
                        CategoryCount++;
                        nme.Add(prov.GetContent(pi[j]).Title);      // displayname
                        lnk.Add( pi[j].FullName);// linkname
                        k++;
                    }
                    for (i = 0; i < ci.Length; i++)
                    {
                        string t;
                        if (snspace != "") t = snspace + "." + category; else t = category;
                        if (ci[i].ToString() == t)
                        {
                            CategoryCount++;
                            nme.Add(prov.GetContent(pi[j]).Title);      // displayname
                            lnk.Add(pi[j].FullName);// linkname
                            k++;
                        }
                    }
                }
            }//cat.
            else
	        {                                       // count the pages that were 'saved as draft' in the editor screen as if it were a category
                ScrewTurn.Wiki.PluginFramework.PageInfo[] pi = prov.GetPages(nspace);// alle pagina's van de xx namespace ophalen
                for (j = 0; j < pi.Length; j++)
                {
                    PageContent pc = prov.GetDraft(pi[j]);
                    if (pc != null && pc.Content.Length > 0)
                    {
                        CategoryCount++;
                        nme.Add(prov.GetContent(pi[j]).Title);      // displayname
                        lnk.Add(pi[j].FullName);// linkname
                        k++;
                    }
                }
            }//draft

            //  sort the list by name first (todo better sort...)
            for (i = 0; i < CategoryCount; i++)
            {
                for (j = 0; j < CategoryCount; j++)
                {
                    if (string.Compare (nme[i] , nme[j])<0)
                    {
                        string t = nme[i];
                        nme[i] = nme[j];
                        nme[j] = t;
                        t = lnk[i];
                        lnk[i] = lnk[j];
                        lnk[j] = t;
                    }
                }
            }

            // write result, 3 cases: as a list; as a table; as a number.

            if (list == "*" || list == "#")         // write result as a list
            {
                if (CategoryCount > 0)
                {                                   // write list
                    if (list == "*") result.Append(string.Format("<ul>")); else result.Append(string.Format("<ol>"));
                    for (j = 0; j < CategoryCount; j++)
                    {
                        result.Append(string.Format("<li><a href=\"{0}.ashx\">{1}</a></li>", lnk[j], nme[j]));
                    }
                    if (list == "*") result.Append(string.Format("</ul>")); else result.Append(string.Format("</ol>"));
                }
                else
                {                                   // write empty list
                    if (list == "*") result.Append(string.Format("<ul>")); else result.Append(string.Format("<ol>"));
                    result.Append(string.Format("<li>{0}</li>",zero));
                    if (list == "*") result.Append(string.Format("</ul>")); else result.Append(string.Format("</ol>"));
                }
            }
            else
            {                                       // write result as a table
                if (list == "T" || list == "t")
                {
                    if (CategoryCount > 0)
                    {                                   // write table, header
                        result.Append(string.Format("<table><tr><th>{0}", category == "-" ? "uncategorized" : category));
                        if (category != "save_as_draft")
                        {                               // add link for details view
                            result.Append(string.Format("<a href=\"{0}AllPages.aspx?Cat={1}\" title=\"details\"> *</a>", snspace.Length > 0 ? snspace + "." : "", category));
                        }
                        result.Append("</th></tr>");
                        for (j = 0; j < CategoryCount; j++)     // write table, content
                        {
                            result.Append(string.Format("<tr><td><a href=\"{0}.ashx\">{1}</a></td></tr>", lnk[j], nme[j]));
                        }
                        result.Append(string.Format("</table>"));
                    }
                    else
                    {                               // write empty table
                        result.Append(string.Format("<table><tr><th>{0}</th></tr><tr><td>{1}</td></tr></table>", category == "-" ? "uncategorized" : category, zero));
                    }
                }
                else
                {                                   // write result as a number
                    result.Append(string.Format("{0}", CategoryCount));
                }
            }

            //result.Append(string.Format("--{0}--{1}--", Pname, nspace.ToString()));//debug
            //result.Append(string.Format("<span style=\"background-color:{0};\">{1}</span>", color, innerXml));
            return result.ToString();
        }        

        /// <summary>
        /// Gets the root attributes.
        /// </summary>
        /// <param name="doc">The XML document.</param>
        /// <param name="pattern">The pattern.</param>
        private static void GetRootAttributes(XmlDocument doc, out string category, out string list, out string zero)
        {
            XmlNodeList root = doc.GetElementsByTagName("jk");

            category = TryGetAttribute(root[0], "category");
            if (String.IsNullOrEmpty(category))
                category = "";
            list = TryGetAttribute(root[0], "list");
            if (String.IsNullOrEmpty(list))
                list = "";
            zero = TryGetAttribute(root[0], "zero");
            if (String.IsNullOrEmpty(zero))
                zero = "no pages found";
        }

        /// <summary>
        /// Tries to get the value of an attribute.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="attribute">The name of the attribute.</param>
        /// <returns>The value of the attribute or <c>null</c> if no value is available.</returns>
        private static string TryGetAttribute(XmlNode node, string attribute)
        {
            XmlAttribute attr = node.Attributes[attribute];
            if (attr != null) return attr.Value;
            else return null;
        }

        //private static void GetInnerXml(XmlDocument doc, out string innerXml)
        //{
        //    XmlNodeList root = doc.GetElementsByTagName("jk");

        //    innerXml = root[0].InnerXml;
        //}

        /// <summary>
        /// Finds and removes the first occurrence of the XML markup.
        /// </summary>
        /// <param name="buffer">The buffer.</param>
        /// <returns>The index-content data.</returns>
        private static KeyValuePair<int, string> FindAndRemoveFirstOccurrence(StringBuilder buffer)
        {
            Match match = XmlRegex.Match(buffer.ToString());

            if (match.Success)
            {
                buffer.Remove(match.Index, match.Length);

                return new KeyValuePair<int, string>(match.Index, match.Value);
            }

            return new KeyValuePair<int, string>(-1, null);
        }

        /// <summary>
        /// Logs a warning.
        /// </summary>
        /// <param name="message">The message.</param>
        private void LogWarning(string message)
        {
            if (_enableLogging)
            {
                _host.LogEntry(message, LogEntryType.Warning, null, this);
            }
        }

        /// <summary>
        /// Prepares the title of an item for display (always during phase 3).
        /// </summary>
        /// <param name="title">The input title.</param>
        /// <param name="context">The context information.</param>
        /// <returns>The prepared title (no markup allowed).</returns>
        public string PrepareTitle(string title, ContextInformation context)
        {
            return title;
        }

        /// <summary>
        /// Initializes the Storage Provider.
        /// </summary>
        /// <param name="host">The Host of the Component.</param>
        /// <param name="config">The Configuration data, if any.</param>
        /// <remarks>If the configuration string is not valid, the methoud should throw a <see cref="InvalidConfigurationException"/>.</remarks>
        public void Init(IHostV30 host, string config)
        {
            this._host = host;
            this._config = config != null ? config : "";

            if (this._config.ToLowerInvariant() == "nolog") _enableLogging = false;
        }

        /// <summary>
        /// Method invoked on shutdown.
        /// </summary>
        /// <remarks>This method might not be invoked in some cases.</remarks>
        public void Shutdown()
        {
            // Nothing to do
        }

        /// <summary>
        /// Gets the Information about the Provider.
        /// </summary>
        public ComponentInformation Information
        {
            get { return Info; }
        }

        /// <summary>
        /// Gets a brief summary of the configuration string format, in HTML. Returns <c>null</c> if no configuration is needed.
        /// </summary>
        public string ConfigHelpHtml
        {
            get { return "This plugin counts the number of pages in a category or produces a list of the pages in a category.<br>use: &lt;jk category=\"xxx|save_as_draft|-\" [list=\"*|#|t\"] [zero=\"no pages\"] /&gt;"; }            // use: <jk category="xxx|save_as_draft|-" [list="*|#|t"] [zero="no pages"] />

        }
    }
}
